package pt.compflow;

import java.util.Iterator;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pt.compflow.flowontology.model.Activity;
import pt.compflow.flowontology.model.Event;
import pt.compflow.flowontology.model.FLOWO;
import pt.compflow.flowontology.model.FlowOntologyModel;
import pt.compflow.flowontology.model.Gateway;
import pt.compflow.flowontology.model.Job;
import pt.compflow.flowontology.model.Loop;
import pt.compflow.flowontology.model.Task;
import pt.compflow.flowontology.model.Workflow;
import pt.compflow.graph.Tree;
import pt.compflow.tools.OntModelHelper;
import pt.ipp.isep.gecad.o2c.model.impl.DateContainer;
import pt.ipp.isep.gecad.o2c.model.impl.DateContainer.DateContainerType;

import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.rdf.model.impl.ResourceImpl;
import com.hp.hpl.jena.vocabulary.DC;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;

public class InstantiatorImpl implements Instantiator {
	
	private static final Logger LOG = LoggerFactory.getLogger(InstantiatorImpl.class);
	
	public InstantiatorImpl() {
	}
	
	/**
	 * Initializes an Activity-Definition instance with common properties and values
	 */
	protected Activity initActivityInstance(OntModelHelper ontModelHelper, FlowOntologyModel jobModel, FlowOntologyModel engineModel, Resource clazz, Workflow wf, Activity instance, OntClass genericClass) {
		// set the activity type
		instance.addProperty(RDF.type, clazz);
		
		StmtIterator it = clazz.listProperties(RDFS.label);
		while(it.hasNext()) {
			Statement stmt = it.next();
			instance.addProperty(RDFS.label, jobModel.createLiteral(stmt.getObject().asLiteral().getString()));
		}
		it = clazz.listProperties(DC.description);
		while(it.hasNext()) {
			Statement stmt = it.next();
			instance.addProperty(DC.description, jobModel.createLiteral(stmt.getObject().asLiteral().getString()));
		}
		
		// set the parent workflow
		if(wf != null) {
			wf.addHasActivityActivity(instance);
			instance.addActivityOfWorkflow(wf);
		}
		
		// set the priority
		Resource priority = FLOWO.medium; 
		Tree priorityTree = ontModelHelper.getApplicableEdgeDestinationsClosure(FLOWO.hasPriority.getURI(), clazz.getURI(), FLOWO.Activity.getURI());
		
		if(priorityTree != null && !priorityTree.isEmpty()) {
			Iterator<Tree> priorityTreeIt = priorityTree.iterator();
			priority = jobModel.getResource(priorityTreeIt.next().getNode());
		}
		instance.addProperty(FLOWO.hasPriority, priority);
		
		// set the timeout
		long timeout = 0;	// default none 
		Tree timeoutTree = ontModelHelper.getApplicableEdgeDestinationsClosure(FLOWO.hasTimeout.getURI(), clazz.getURI(), FLOWO.Activity.getURI());
		
		if(timeoutTree != null && !timeoutTree.isEmpty()) {
			Iterator<Tree> timeoutTreeIt = timeoutTree.iterator();
			timeout = Long.parseLong(timeoutTreeIt.next().getNode());
		}
		instance.addLiteral(FLOWO.hasTimeout, timeout);
		
		// set creation date and initial state
		instance.setHasCreationDate(new DateContainer(DateContainerType.DATETIME));
		instance.removeAll(FLOWO.hasState);
		instance.addProperty(FLOWO.hasState, FLOWO.notStarted);
		
		return instance;
	}
	
	public Workflow getWorkflowInstance(OntModelHelper ontModelHelper, FlowOntologyModel jobModel, FlowOntologyModel engineModel, Resource clazz, Workflow parentWf, Job job) {
		if(!ontModelHelper.hasSubClassClosure(FLOWO.Workflow.getURI(), clazz.getURI())) {
			LOG.warn("OntClass " + clazz.getURI() + " is not a subClassOf " + FLOWO.Workflow.getURI());
			return null;
		}
		
		String wfOntology = clazz.getNameSpace().substring(0, clazz.getNameSpace().length()-1);
		if(parentWf != null) {
			Workflow topParent = parentWf.getRootWorkflow();		
			wfOntology = topParent.getOntology(null);
		}
		
		// connect to workflow data model (= to parentWfUri if there's a parentWf; = to wfUri if no parentWf exists)
		Workflow wf = jobModel.createWorkflow(getUniqueWorkflowId(jobModel, null));
		initActivityInstance(ontModelHelper, jobModel, engineModel, clazz, parentWf, wf, FLOWO.Workflow);
				
		// the ontology for this workflow is given by the namespace of its class
		// or if a parentWf exists, the ontology of the root parent
		wf.setOntology(wfOntology, null);
												
		// instantiate and set first activity
		Tree firstActivityTree = ontModelHelper.getApplicableEdgeDestinations(FLOWO.hasFirstActivity.getURI(), clazz.getURI());
		
		if(firstActivityTree == null || firstActivityTree.isEmpty()) 
			throw new RuntimeException("Invalid Restriction on firstActivity for " + clazz.getURI());
		
		Iterator<Tree> firstActivityTreeIt = firstActivityTree.iterator();
		while(firstActivityTreeIt.hasNext()) {
			Tree firstActivity = firstActivityTreeIt.next();
			Resource firstActClass = ontModelHelper.getFlowModel().getResource(firstActivity.getNode()); 
			Activity firstAct = getActivityInstance(ontModelHelper, jobModel, engineModel, firstActClass, wf, null);
			wf.addProperty(FLOWO.hasFirstActivity, firstAct);
		}
		
		// #### LOOP CODE
		if(ontModelHelper.hasSubClassClosure(FLOWO.Loop.getURI(), clazz.getURI())) {
			Tree maxItTree = ontModelHelper.getApplicableEdgeDestinationsClosure(FLOWO.maxIterations.getURI(), clazz.getURI(), FLOWO.Loop.getURI());
			int maxIts = Integer.valueOf(maxItTree.iterator().next().getNode());
			wf.addProperty(RDF.type, FLOWO.Loop);
			Loop loop = wf.as(Loop.class);
			loop.setMaxIterations(maxIts);
			loop.setIteration(0);
		}
		// #### LOOP CODE
				
		return wf;
	}
	
	public Task getTaskInstance(OntModelHelper ontModelHelper, FlowOntologyModel jobModel, FlowOntologyModel engineModel, Resource clazz, Workflow wf) {
		if(!ontModelHelper.hasSubClassClosure(FLOWO.Task.getURI(), clazz.getURI())) {
			LOG.warn("OntClass " + clazz.getURI() + " is not a subClassOf " + FLOWO.Task.getURI());
			return null;
		}
				
		Task task = jobModel.createTask(getUniqueTaskId(jobModel, null, null));
		initActivityInstance(ontModelHelper, jobModel, engineModel, clazz, wf, task, FLOWO.Task);
				
		// set assignment per unit
		task.setHasAssignmentsPerUnit(1);
		
		Tree assignmentsPerUnitTree = ontModelHelper.getApplicableEdgeDestinationsClosure(FLOWO.hasAssignmentsPerUnit.getURI(), clazz.getURI(), FLOWO.Task.getURI());
		if(assignmentsPerUnitTree != null && !assignmentsPerUnitTree.isEmpty()) {
			int val = Integer.valueOf(assignmentsPerUnitTree.iterator().next().getNode());
			if(val > 0) task.setHasAssignmentsPerUnit(val);
			else LOG.warn("Ignoring hasAssignmentsPerUnit value for " + clazz.getURI() + ". Must be greater than 0!");
		}
		
		// set interfaces to deliver (get them from the engineModel)
		if(!ontModelHelper.setTaskInterfacesToDeliver(engineModel, clazz.getURI(), task)) {
			throw new RuntimeException("No task interfaces to deliver " + clazz.getURI() + " were found in the engine environment!");
		}
		
		return task;
	}
	
	public Gateway getGatewayInstance(OntModelHelper ontModelHelper, FlowOntologyModel jobModel, FlowOntologyModel engineModel, Resource clazz, Workflow wf) {
		if(!ontModelHelper.hasSubClassClosure(FLOWO.Gateway.getURI(), clazz.getURI())) {
			LOG.warn("OntClass " + clazz.getURI() + " is not a subClassOf " + FLOWO.Gateway.getURI());
			return null;
		}
		
		Gateway gateway = jobModel.createGateway(getUniqueGatewayId(jobModel, null, null));
		initActivityInstance(ontModelHelper, jobModel, engineModel, clazz, wf, gateway, FLOWO.Gateway);
		
		return gateway;
	}
	
	public Event getEventInstance(OntModelHelper ontModelHelper, FlowOntologyModel jobModel, FlowOntologyModel engineModel, Resource clazz, Workflow wf) {
		if(!ontModelHelper.hasSubClassClosure(FLOWO.Event.getURI(), clazz.getURI())) {
			LOG.warn("OntClass " + clazz.getURI() + " is not a subClassOf " + FLOWO.Event.getURI());
			return null;
		}
		
		Event event = jobModel.createEvent(getUniqueEventId(jobModel, null, null));
		initActivityInstance(ontModelHelper, jobModel, engineModel, clazz, wf, event, FLOWO.Event);
						
		if(!ontModelHelper.setEventInterfacesToDeliver(engineModel, clazz.getURI(), event)) {
			throw new RuntimeException("No event interfaces to deliver " + clazz.getURI() + " were found in the engine environment!");
		}
			
		return event;
	}
	
	public Activity getActivityInstance(OntModelHelper ontModelHelper, FlowOntologyModel instModel, FlowOntologyModel engineModel, Resource clazz, Workflow wf, Job job) {
		
		Activity instance = null;
		
		if(ontModelHelper.hasSubClassClosure(FLOWO.Workflow.getURI(), clazz.getURI())) {
			instance = getWorkflowInstance(ontModelHelper, instModel, engineModel, clazz, wf, job);	
		} else if(ontModelHelper.hasSubClassClosure(FLOWO.Task.getURI(), clazz.getURI())) {
			instance = getTaskInstance(ontModelHelper, instModel, engineModel, clazz, wf);
		} else if(ontModelHelper.hasSubClassClosure(FLOWO.Gateway.getURI(), clazz.getURI())) {
			instance = getGatewayInstance(ontModelHelper, instModel, engineModel, clazz, wf);	
		} else if(ontModelHelper.hasSubClassClosure(FLOWO.Event.getURI(), clazz.getURI())) {
			instance = getEventInstance(ontModelHelper, instModel, engineModel, clazz, wf);	
		} else 
			throw new RuntimeException("Class " + clazz.getURI() + " is not a concrete sub-class of Activity (Workflow, Task, Gateway or Event)");
			
		return instance;
	}
	
	public String getUniqueJobId(Model model) {
		return getUniqueId("job", model);
	}
	
	public String getUniqueJobTemplateId(Model model) {
		return getUniqueId("jobTemplate", model);
	}
	
	public String getUniqueWorkflowId(Model model, Job job) {
		return getUniqueId((job != null ? job.getLocalName() + "_" : "") + "wfw", model);
	}
	
	public String getUniqueTaskId(Model model, Job job, Workflow wf) {
		return getUniqueId((job != null ? job.getLocalName() + "_" : "") + (wf != null ? wf.getLocalName() + "_" : "") + "tsk", model);
	}
	
	public String getUniqueGatewayId(Model model, Job job, Workflow wf) {
		return getUniqueId((job != null ? job.getLocalName() + "_" : "") + (wf != null ? wf.getLocalName() + "_" : "") + "gat", model);
	}
	
	public String getUniqueEventId(Model model, Job job, Workflow wf) {
		return getUniqueId((job != null ? job.getLocalName() + "_" : "") + (wf != null ? wf.getLocalName() + "_" : "") + "evt", model);
	}
	
	public String getUniqueActivityId(Model model, Job job, Workflow wf) {
		return getUniqueId((job != null ? job.getLocalName() + "_" : "") + (wf != null ? wf.getLocalName() + "_" : "") + "act", model);
	}
	
	public String getUniqueAssignmentId(Model model, Job job, Workflow wf, Task task) {
		return getUniqueId((job != null ? job.getLocalName() + "_" : "") + (wf != null ? wf.getLocalName() + "_" : "") + (task != null ? task.getLocalName() + "_" : "") + "asg", model);
	}
		
	public String getUniqueId(String id, Model model) {
		UUID uuid = UUID.randomUUID();
		
		while(model.containsResource(new ResourceImpl(FLOWO.getURI(), id + "-" + uuid)))
			uuid = UUID.randomUUID();
			
		return id + "-" + uuid;
	}
	
}
